﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sinacor.Infra.Common.Validation;
using Sinacor.Infra.Service.ExceptionHandling;
using Sinacor.Infra.Common.Exceptions;

namespace Sinacor.Infra.Service.Validation.BusinessRules
{
	/// <summary>
	/// Validador de regras de negocio
	/// </summary>
	public class BusinessRulesValidator
	{
		public bool StopOnFirstValidationError { get; set; }
		public bool HasErrors { get; set; }
		Dictionary<string, IBusinessRuleValidation> _validatorList;
		Dictionary<string, List<ValidationError>> _errorList;

		public BusinessRulesValidator()
		{
			this._validatorList = new Dictionary<string, IBusinessRuleValidation>();
			this._errorList = new Dictionary<string, List<ValidationError>>();
			this.StopOnFirstValidationError = true;
		}

		/// <summary>
		/// Adiciona uma classe de validação ao Validador.
		/// </summary>
		/// <param name="key">Chave usada para identificar a regra de validação (deve ser única)</param>
		/// <param name="validator">Classe de validação</param>
		public void AddValidation(string key, IBusinessRuleValidation validator)
		{
			this.ValidateKey(key);

			if (validator == null)
				throw new ArgumentNullException("validator");

			if (this._validatorList.ContainsKey(key))
				throw new Exception("Identification key already exists");
			else
				this._validatorList.Add(key, validator);
		}

		/// <summary>
		/// Remove uma classe de validação do Validador.
		/// </summary>
		/// <param name="key">Chave usada para identificar a regra de validação </param>
		public void RemoveValidation(string key)
		{
			this.ClearErrorsAt(key);
			this._validatorList.Remove(key);
		}

		/// <summary>
		/// Remove todas as classes de validação do Validador.
		/// </summary>
		public void RemoveAllValidations()
		{
			this._validatorList.Clear();
			this._errorList.Clear();
			this.HasErrors = false;
		}

		/// <summary>
		/// Aciona cada uma das classes de validação contidas no Validator. 
		/// Caso ocorra um ou mais erros, o retorno será false.
		/// Caso a propriedade StopOnFirstError esteja acionada, a validação será encerrada no primeiro erro.
		/// </summary>
		/// <returns></returns>
		public bool IsAllValid()
		{
			int errorsCount = 0;

			foreach (KeyValuePair<string, IBusinessRuleValidation> item in this._validatorList)
			{
				if (this.StopOnFirstValidationError && errorsCount > 0)
					return false;

				item.Value.Validate();

				List<ValidationError> errors = item.Value.GetErrors();
				this.StoreErrors(item.Key, errors);
				errorsCount += errors.Count;
				errors.Clear();
			}

			return (errorsCount == 0);
		}

		/// <summary>
		/// Aciona cada a classe de validação identificada pela chave.
		/// Caso ocorra um ou mais erros, o retorno será false.
		/// </summary>
		/// <param name="key">Chave usada para identificar a regra de validação</param>
		/// <returns></returns>
		public bool IsValidAt(string key)
		{
			this.ValidateKey(key);
			this.ValidateKeyInValidationList(key);

			IBusinessRuleValidation item = this._validatorList[key];
			item.Validate();

			List<ValidationError> errors = item.GetErrors();
			this.StoreErrors(key, errors);
			bool valid = (errors.Count == 0);
			errors.Clear();

			return valid;
		}

		/// <summary>
		/// Aciona cada uma das classes de validação contidas no Validator. 
		/// Caso ocorra um ou mais erros, será lançada uma ValidationException.
		/// Caso a propriedade StopOnFirstError esteja acionada, a validação será encerrada no primeiro erro.
		/// </summary>
		public void Validate()
		{
			if (!this.IsAllValid())
			{
				ValidationException ex = new ValidationException();

				foreach (string key in this.GetAllErrors().Keys)
				{
					this.AddErrorIntoException(this.GetErrorsAt(key), ex);
				}

				throw ex;
			}
		}

		/// <summary>
		/// Verifica se houve erro de validação para uma regra especifica
		/// Necessario ter executado IsAllValid() ou IsValidAt() anteriormente.
		/// </summary>
		/// <param name="key">Chave usada para identificar a regra de validação</param>
		/// <returns></returns>
		public bool HasErrorsAt(string key)
		{
			this.ValidateKey(key);
			this.ValidateKeyInValidationList(key);

			return (this._errorList[key].Count > 0);
		}

		/// <summary>
		/// Retorna todos os erros de validação, agrupados pelas chaves de identificação
		/// Necessario ter executado IsAllValid() ou IsValidAt() anteriormente.
		/// </summary>
		/// <returns></returns>
		public Dictionary<string, List<ValidationError>> GetAllErrors()
		{
			return (this._errorList.Count > 0 ? this._errorList : null);
		}

		/// <summary>
		/// Retorna os erros de validação para uma regra especifica
		/// Necessario ter executado IsAllValid() ou IsValidAt() anteriormente.
		/// </summary>
		/// <param name="key"></param>
		/// <returns></returns>
		public List<ValidationError> GetErrorsAt(string key)
		{
			this.ValidateKey(key);
			this.ValidateKeyInValidationList(key);

			if (this._errorList.ContainsKey(key) && this._errorList[key].Count > 0)
				return this._errorList[key];
			else
				return null;
		}

		/// <summary>
		/// Retorna a classe de validação
		/// </summary>
		/// <param name="key"></param>
		/// <returns></returns>
		public T GetValidation<T>(string key) where T : BusinessRules.IBusinessRuleValidation
		{
			this.ValidateKey(key);

			if (!this._validatorList.ContainsKey(key))
				throw new Exception("Identification key not exists");

			return (T)this._validatorList[key];
		}

		/// <summary>
		/// Limpa a lista de erros de todas as validações
		/// Só haverá erros se executado IsAllValid() ou IsValidAt() anteriormente.
		/// </summary>
		public void ClearAllErrors()
		{
			this._errorList.Clear();
			this.HasErrors = false;
		}

		/// <summary>
		/// Limpa a lista de erros de uma validação especifica.
		/// Só haverá erros se executado IsAllValid() ou IsValidAt() anteriormente.
		/// </summary>
		/// <param name="key"></param>
		public void ClearErrorsAt(string key)
		{
			this.ValidateKey(key);
			this.ValidateKeyInValidationList(key);

			if (this._errorList.ContainsKey(key))
				this._errorList.Remove(key);

			if (this._errorList.Values.Count == 0)
				this.HasErrors = false;
		}

		/// <summary>
		/// Armazena os erros ocorridos em uma classe de validação de negocio.
		/// </summary>
		/// <param name="key">Chave usada para identificar a regra de validação</param>
		/// <param name="errorList">Lista de erros de validação</param>
		void StoreErrors(string key, List<ValidationError> errorList)
		{
			if (errorList != null && errorList.Count > 0)
			{
				this.HasErrors = true;

				if (!this._errorList.ContainsKey(key))
					this._errorList.Add(key, new List<ValidationError>());

				this._errorList[key].AddRange(errorList);
			}
		}

		/// <summary>
		/// Valida se a key informada é válida
		/// </summary>
		/// <param name="key"></param>
		void ValidateKey(string key)
		{
			if (string.IsNullOrEmpty(key))
				throw new ArgumentNullException("key", "The business rule requires an identification key");
		}

		/// <summary>
		/// Valida se a key informada consta na lista de validações
		/// </summary>
		/// <param name="key"></param>
		void ValidateKeyInValidationList(string key)
		{
			if (!this._validatorList.ContainsKey(key))
				throw new ArgumentException(string.Format("Invalid identification key '{0}'", key), "key");
		}

		/// <summary>
		/// Adiciona cada um dos códigos de exceção a lista do ValidationException com suporte a criação da tradução
		/// </summary>
		/// <param name="errorList">Lista de Erros</param>
		/// <param name="validationException">Exceção</param>
		void AddErrorIntoException(List<ValidationError> errorList, ValidationException validationException)
		{
            int i = 0;

			foreach (ValidationError error in errorList)
			{
                validationException.ValidationErrors.Add(i, error);
                i++;

                //if (error.Parameters != null && error.Parameters.Count > 0)
                //    validationException.AddError(error.ErrorNumber, error.Parameters.ToArray());
                //else
                //    validationException.AddError(error.ErrorNumber);
			}
		}

		/// <summary>
		/// Adiciona uma classe de validação ao Validador.
		/// </summary>
		/// <param name="validator">Classe de validação (A key será o Type da Classe)</param>
		public void AddValidation(IBusinessRuleValidation validator)
		{
			if (validator == null)
				throw new ArgumentNullException("validator");

			string key = validator.GetType().Name;

			if (this._validatorList.ContainsKey(key))
				throw new Exception("Identification key already exists");
			else
				this._validatorList.Add(key, validator);
		}

		/// <summary>
		/// Retorna a classe de validação
		/// A key será o type da classe
		/// </summary>
		/// <param name="key"></param>
		/// <returns></returns>
		public T GetValidation<T>() where T : BusinessRules.IBusinessRuleValidation
		{
			string key = typeof(T).Name;
			this.ValidateKey(key);
			this.ValidateKeyInValidationList(key);
			return (T)this._validatorList[key];
		}
	}
}
